/*
 * MySQL Cluster SNMP monitoring subagent
 * Copyright (C) 2011, Steven Ayre <steveayre@gmail.com>
 *
 * Version: MPL 1.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is MySQL Cluster SNMP monitoring subagent
 *
 * The Initial Developer of the Original Code is
 * Steven Ayre <steveayre@gmail.com>
 * Portions created by the Initial Developer are Copyright (C)
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Steven Ayre <steveayre@gmail.com>
 *
 * thread.c -- Thread helper code
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
#include <sys/time.h>
#include <libdaemon/daemon.h>
#include "thread.h"

struct _thread {
	pthread_mutex_t mutex;
	pthread_cond_t cond;
	pthread_t id;
	char *name;
	int (*thread_main) (thread_t*,void*);
	void *param;
	int running;
	int exit_status;
};

static void* thread_runtime(void *param)
{
	thread_t *thread = (thread_t*)param;
	int status;

	status = thread->thread_main(thread, thread->param);

	pthread_mutex_lock(&thread->mutex);
	thread->running     = 0;
	thread->exit_status = status;
	daemon_log(LOG_ERR, "%s: Finished thread (exit status %d)", thread->name, status);
	pthread_mutex_unlock(&thread->mutex);

	return NULL;
}

thread_t* thread_start(const char *name, int (*thread_main) (thread_t*,void*), void *param)
{
	thread_t *thread = NULL;

	daemon_log(LOG_INFO, "%s: Starting thread...", name);

	if (!(thread = (thread_t*)calloc(1, sizeof(thread_t)))) {
		return NULL;
	}
	if (!(thread->name = strdup(name))) {
		free(thread);
		return NULL;
	}
	thread->running = 1;
	thread->thread_main = thread_main;
	thread->param = param;
	pthread_mutex_init(&thread->mutex, NULL);
	pthread_cond_init(&thread->cond, NULL);

	pthread_mutex_lock(&thread->mutex);
	if ((errno = pthread_create(&thread->id, NULL, thread_runtime, thread)) != 0) {
		pthread_mutex_unlock(&thread->mutex);
		pthread_mutex_destroy(&thread->mutex);
		pthread_cond_destroy(&thread->cond);
		free(thread->name);
		free(thread);
		return NULL;
	}
	thread->running = 1;
	pthread_mutex_unlock(&thread->mutex);
	
	daemon_log(LOG_INFO, "%s: Started thread", thread->name);
	return thread;
}

void thread_stop(thread_t *thread)
{
	daemon_log(LOG_INFO, "%s: Stopping thread...", thread->name);

	pthread_mutex_lock(&thread->mutex);
	thread->running = 0;
	pthread_cond_signal(&thread->cond);
	pthread_mutex_unlock(&thread->mutex);
}

void thread_destroy(thread_t *thread)
{
	thread_stop(thread);

	pthread_join(thread->id, NULL);

	daemon_log(LOG_INFO, "%s: Stopped thread (exit status %d)", thread->name, thread->exit_status);

	pthread_mutex_destroy(&thread->mutex);
	pthread_cond_destroy(&thread->cond);
	free(thread->name);
	free(thread);
}

int thread_running(thread_t *thread)
{
	int running;

	pthread_mutex_lock(&thread->mutex);
	running = thread->running;
	pthread_mutex_unlock(&thread->mutex);

	return running;
}

void thread_yield(thread_t *thread, int usec)
{
	pthread_mutex_lock(&thread->mutex);
	if (thread->running) {
		struct timespec ts;
		struct timeval now;
		gettimeofday(&now, NULL);
		ts.tv_sec  = now.tv_sec + (usec / 1000000);
		ts.tv_nsec = (now.tv_usec + (usec % 1000000)) * 1000;
		while(ts.tv_nsec > 1000000000) {
			ts.tv_sec++;
			ts.tv_nsec -= 1000000000;
		}
		pthread_cond_timedwait(&thread->cond, &thread->mutex, &ts);
	}
	pthread_mutex_unlock(&thread->mutex);
}

int thread_exit_status(thread_t *thread)
{
	int status;

	pthread_mutex_lock(&thread->mutex);
	status = thread->exit_status;
	pthread_mutex_unlock(&thread->mutex);

	return status;
}

const char* thread_name(thread_t *thread)
{
	return thread->name;
}

